Skip to content

Fix: create per-task DI scope in ExecuteToolAsTaskAsync to prevent ObjectDisposedException#1433

Draft
Copilot wants to merge 2 commits intomainfrom
copilot/research-issue-1430
Draft

Fix: create per-task DI scope in ExecuteToolAsTaskAsync to prevent ObjectDisposedException#1433
Copilot wants to merge 2 commits intomainfrom
copilot/research-issue-1430

Conversation

Copy link
Contributor

Copilot AI commented Mar 15, 2026

Summary

Fixes #1430.

Root cause

When a tool with TaskSupport = ToolTaskSupport.Required (or Optional called with task metadata) is invoked, ExecuteToolAsTaskAsync schedules the tool on a fire-and-forget Task.Run and returns immediately with CallToolResult { Task = ... }. Control flows back to InvokeHandlerAsync, whose finally block then disposes the per-request IAsyncScope — before the background task has run at all.

When the thread-pool eventually executes the background task and calls tool.InvokeAsync(request, ...), AIFunctionMcpServerTool.InvokeAsync wraps request.Services (the now-disposed scope's ServiceProvider) in a RequestServiceProvider and uses it to resolve DI parameters. Since the scope is already disposed, this throws ObjectDisposedException — even for singleton registrations, because the call still goes through the disposed scope's IServiceProvider.

Fix

At the top of the Task.Run body in ExecuteToolAsTaskAsync, create a fresh IAsyncScope from the server's root Services — exactly mirroring what InvokeScopedAsync does for synchronous tool calls — and assign its ServiceProvider to request.Services. The scope is disposed in the finally block alongside the existing cleanup. The background task now has its own scope whose lifetime matches the tool's execution, not the HTTP request that scheduled it.

The logic is directly analogous to how scoped services work for synchronous requests: each unit of work (request or background task) gets its own scope.

Changes

  • src/ModelContextProtocol.Core/Server/McpServerImpl.cs — create a fresh AsyncServiceScope at the start of the Task.Run lambda when _servicesScopePerRequest is true; replace request.Services; dispose in finally.
  • tests/ModelContextProtocol.Tests/Server/McpServerTaskAugmentedValidationTests.cs — regression test: registers a scoped DI service (ITaskToolDiService), creates a TaskSupport = Required tool that resolves it as a method parameter, calls it with task metadata, polls to completion, and asserts the task reached McpTaskStatus.Completed (before the fix it would reach Failed from ObjectDisposedException).

Security

No security impact. No new public API surface. No changes to authentication, authorization, or data handling.

Copilot AI and others added 2 commits March 15, 2026 11:56
… add regression test

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP task will dispose the taskstore which cause invoke tool failed

2 participants